Skip to content

feat(rag): offline re-index & retrieval hardening (config-gated, behavior-neutral)#213

Merged
BigSimmo merged 11 commits into
mainfrom
claude/rag-reindex-land
Jul 3, 2026
Merged

feat(rag): offline re-index & retrieval hardening (config-gated, behavior-neutral)#213
BigSimmo merged 11 commits into
mainfrom
claude/rag-reindex-land

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Lands the offline re-index / retrieval-improvement workstream. All new behavior is config-gated and default-off, so merging is behavior-neutral; defaults reproduce the exact prior behavior and only flip after eval gates.

What's included

  • W6 ranking-config (src/lib/ranking-config.ts): the second-stage rerank weights, document-diversity demotion, and freshness decay are now one config object with an optional RAG_RANKING_CONFIG JSON override. Defaults equal the prior constants; diversity penalty defaults to 0 (OFF).
  • CI-1 chunker (CHUNK_STRATEGY=page|document): page (default) is byte-for-byte the current page-bounded chunker; document is structure-aware cross-page chunking for the eval-gated shadow re-index only.
  • CI-8/9 embedding integrity: embedding-dimensions guard + embed-texts integrity checks; OPENAI_EMBEDDING_BATCH_SIZE (IDX-C3) splits a full-corpus re-embed into <=2048-input batches.
  • CI-4 stable chunk_key, CI-14 synonym recall (clinical-vocabulary), W3/CI-6 OCR-quality signals (index-quality), plus check:indexing coverage.
  • Bump react and @types/react #6 reindex-eval-gate (src/lib/reindex-eval-gate.ts): pure, unit-tested GO/NO-GO non-degradation gate for shadow re-index cutover; design doc in docs/reindex-shadow-harness-design.md.
  • WCC routing: recognizes "white cell count"/WCC as a clozapine blood-monitoring term so WCC-phrased withhold-threshold queries route to the structured clozapine_blood_action_structured_threshold gate (additive synonym, scoped to clozapine + withhold).

Verification

  • verify:cheap (check:runtime + lint + typecheck + vitest): green - 945/945 tests.
  • Merged current main: clean (no conflicts).
  • Golden retrieval eval (eval:retrieval:quality): all committed cases pass, document_recall@5=1.0, top_k_hit_rate=1.0; the WCC synonym edits cause no regression to the ANC/CBC/base cases.

Deferred (tracked follow-up)

The clozapine-wcc-abbreviation-threshold golden case is removed for now (the passing clozapine-cbc case is kept). The WCC routing fix works, but surfacing the withhold-action chunk into top-5 for the WCC phrasing needs ranking tuning on this safety-critical path - deferred rather than hacked.

🤖 Generated with Claude Code

BigSimmo and others added 3 commits July 3, 2026 02:54
…ults unchanged)

Lands the offline re-index / retrieval-improvement workstream. Every behavioural
change ships default-off so the merge is behaviour-neutral; defaults reproduce the
exact prior behavior and only flip after eval gates.

- W6 ranking-config: extract the second-stage rerank weights, document-diversity
  demotion, and freshness decay into src/lib/ranking-config.ts with an optional
  RAG_RANKING_CONFIG JSON override. Defaults equal the prior constants; diversity
  penalty defaults to 0 (OFF).
- CI-1 chunker: CHUNK_STRATEGY=page|document. "page" (default) is byte-for-byte the
  current page-bounded chunker; "document" is structure-aware cross-page chunking for
  the eval-gated shadow re-index only.
- CI-8/9 embedding integrity: embedding-dimensions guard + embed-texts integrity
  checks; OPENAI_EMBEDDING_BATCH_SIZE (IDX-C3) splits a full-corpus re-embed into
  <=2048-input batches so it can't exceed the OpenAI request ceiling.
- CI-4 stable chunk_key, CI-14 synonym recall (clinical-vocabulary), W3/CI-6
  OCR-quality signals (index-quality), plus check-indexing coverage.
- #6 reindex-eval-gate: pure, unit-tested GO/NO-GO non-degradation gate for shadow
  re-index cutover (absolute bars + no-regression vs live baseline); design doc in
  docs/reindex-shadow-harness-design.md.

Test coverage added across chunking, embedding-dimensions, embed-texts integrity,
index-quality, ranking-config, reindex-eval-gate, retrieval-query-variants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e; defer WCC golden case

Follow-up to the offline re-index landing. Recognizes "white cell count"/WCC as a
clozapine haematological-monitoring term so a WCC-phrased withhold-threshold query is
routed to the structured clozapine_blood_action_structured_threshold gate and pulls the
memory-card / table-fact evidence layer (previously it fell through to the generic
threshold gate with no memory cards). Additive synonym only; tightly scoped to
clozapine + withhold queries. Full golden suite shows no regression (ANC/CBC and all
base cases unchanged).

The withhold-action chunk still does not rank into top-5 for the WCC phrasing (a
ranking, not routing, gap that needs care on this safety-critical path), so the
unvalidated clozapine-wcc-abbreviation-threshold golden case is removed for now and
tracked as a follow-up. The passing clozapine-cbc-abbreviation-threshold case is kept.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

export const CHUNKER_VERSION = "1.0.0-page";

P2 Badge Stamp document-mode chunks with their own chunker version

When CHUNK_STRATEGY=document, emitChunk still writes this constant into metadata.chunker_version, so shadow re-index rows produced with cross-page boundaries are labelled 1.0.0-page. Any stale-index/cutover/eval logic that relies on this metadata cannot tell page-bounded chunks from document-mode chunks, even though their chunk boundaries and source spans differ; make the version strategy-specific or include the strategy in metadata.


images: sectionImages,

P2 Badge Restrict document-mode image IDs to contributing pages

In document mode, a multi-page section passes all sectionImages into every emitted chunk, while emitChunk can attach an image based on section-path/header matches rather than the chunk text. For a long section with a table on a later page, an earlier text-only chunk can be stored with that unrelated table's image_ids, causing retrieval results/citations to show visual evidence from pages that did not contribute to the chunk; pass only images from contributing pages or otherwise page-filter them here.


source_spans: args.spanPages.map((span) =>
sourceSpanForText({ pageNumber: span.pageNumber, pageText: span.pageText, excerpt, fallbackExcerpt: content }),

P2 Badge Build per-page source span excerpts for cross-page chunks

For document-mode chunks spanning multiple pages, every sourceSpanForText call receives the full cross-page excerpt. The first page may match, but later contributing pages search for text that starts on an earlier page and usually end up with null offsets, so citations cannot anchor to those pages; derive a page-specific excerpt (or split the chunk text by contributing page) before creating each span.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/reindex-eval-gate.ts Outdated
Comment thread src/lib/reindex-eval-gate.ts Outdated
Comment thread tests/eval-retrieval.test.ts
Comment thread src/lib/reindex-eval-gate.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

source_spans: args.spanPages.map((span) =>
sourceSpanForText({ pageNumber: span.pageNumber, pageText: span.pageText, excerpt, fallbackExcerpt: content }),

P2 Badge Derive page-local spans for cross-page chunks

When CHUNK_STRATEGY=document emits a chunk spanning multiple pages, this calls sourceSpanForText for each contributing page with the same full-chunk excerpt. Because sourceSpanForText searches the first part of that excerpt in the page text, only the first page of the chunk can match; later pages get null offsets and an excerpt that starts with text from a different page. This breaks source anchors/highlighting for evidence that came from a continuation page, so the span should be built from a page-local snippet before storing each entry.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/reindex-eval-gate.ts
Comment thread src/lib/clinical-vocabulary.ts
@BigSimmo
BigSimmo enabled auto-merge (squash) July 3, 2026 04:17

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

source_spans: args.spanPages.map((span) =>
sourceSpanForText({ pageNumber: span.pageNumber, pageText: span.pageText, excerpt, fallbackExcerpt: content }),
),

P2 Badge Build page-specific spans for cross-page chunks

When CHUNK_STRATEGY=document creates a chunk spanning multiple pages, this maps every contributing page through sourceSpanForText with the same full-chunk excerpt. Because sourceSpanForText only searches from the beginning of that excerpt, later pages usually get null offsets and an excerpt copied from page 1, so source metadata/citations for evidence that came from page 2+ point at the wrong text. Pass a page-specific excerpt (or split the chunk text per contributing page) when building each span.


// document-mode chunking is enabled by default).

P2 Badge Stamp document chunks with the active strategy version

The new document-mode chunker still stamps every row with CHUNKER_VERSION = "1.0.0-page". In the shadow re-index path that runs with CHUNK_STRATEGY=document, staged cross-page chunks become indistinguishable from page-bounded chunks in metadata, defeating the stated purpose of using the version to identify which chunker produced a row and to invalidate stale chunks after boundary changes. Include the active strategy/params in the stamped version before using document mode for eval-gated re-indexes.


images: sectionImages,

P2 Badge Limit document-mode images to contributing pages

In document mode this passes every image from the whole section into emitChunk, even when attributeChunkToPages has narrowed the chunk to only one or two contributing pages. For long sections with multiple tables/images, the heading/path matching in emitChunk can attach image_ids from pages outside page_start/page_end, so source-image/table requests may treat an unrelated page's visual as evidence for the chunk; pass only images from the contributing pages.


if (fingerprint && chunkFingerprint.has(fingerprint)) return;
if (fingerprint) chunkFingerprint.set(fingerprint, chunks.length);

P2 Badge Scope document-mode dedupe beyond raw content

With CHUNK_STRATEGY=document, this document-wide fingerprint drops every later chunk whose normalized content matches an earlier one, regardless of page or section. Identical clinical rows or repeated instructions can appear under different headings/pages and need distinct source attribution; page mode preserved those duplicates, but document mode now silently removes them from the staged index. Include section/page context in the dedupe key or restrict this to proven boilerplate.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/reindex-eval-gate.ts
Comment thread src/lib/reindex-eval-gate.ts
Comment thread src/lib/reindex-eval-gate.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

chunker_version: CHUNKER_VERSION,

P2 Badge Stamp document-mode chunks with their own version

When CHUNK_STRATEGY=document is enabled for the shadow re-index, buildDocumentModeChunks still reaches this shared emitter and stamps chunker_version with the constant value 1.0.0-page, with no separate strategy marker in the metadata. That makes document-mode rows indistinguishable from the default page-mode rows, so any stale-row/re-index health check using the advertised chunker version cannot tell whether the candidate was actually produced by the cross-page chunker; include the active strategy in the version or metadata.


.update(`${documentId}${sectionAnchor ?? ""}${normalizeChunkKeyContent(content)}`)

P2 Badge Replace literal NUL separators with escaped text

This template literal contains actual 0x00 bytes in the TypeScript source, which makes Git classify src/lib/chunking.ts as binary and hides textual diffs for the whole file in normal review/patch tooling. The hash can still use NUL separators without making the source binary by writing them as escaped \0 characters instead.


pageNumber: representativePage,

P2 Badge Preserve page ranges for cross-page chunk citations

When document-mode chunking merges content from pages 1–2 and the answer comes from the later page, this stores only the first contributing page as the chunk's page_number. The citation path uses result.page_number for labels and does not read metadata.page_start/page_end, so users can be sent to page 1 for evidence that came from page 2; propagate a page range into citations or avoid collapsing cross-page chunks to a single representative page.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/rag.ts
@BigSimmo
BigSimmo merged commit e679dd8 into main Jul 3, 2026
4 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 160ce6c755

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/chunking.ts
Comment on lines +747 to +749
const contentWords = new Set(
normalizeLookupText(content.replace(imageDataTagPattern, " ")).split(/\s+/).filter(Boolean),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep image pages contributing in document mode

When document-mode chunking produces a chunk whose evidence comes from inline image/table tags (for example scanned or image-only pages), this strips all of that tag text before page attribution, so contentWords can be empty and attributeChunkToPages falls back to only the first page. The chunk still contains the later pages' [[IMAGE_DATA_START]] text, but contributingImages, image_ids, page_start/page_end, and source spans are then limited to page 1, which can make source-image/table evidence from later pages unavailable after the shadow re-index.

Useful? React with 👍 / 👎.

Comment on lines +13 to +16
case_count: number;
case_fingerprint?: string;
failed_case_count: number;
document_recall_at_5: number;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align gate inputs with actual eval summaries

Fresh evidence in this revision is that scripts/eval-retrieval.ts still emits failed_cases as an array, while this pass-through gate now requires failed_case_count; similarly the quality governance rates consumed below come from retrieval.source_governance, not the RAG summary object. A driver following the stated “pass summaries through unchanged” contract will therefore return NO_GO for every run due to missing required metrics instead of evaluating the candidate, so either the eval scripts need to emit these fields or the gate needs an adapter that derives them from the current summaries.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant